<template>
    <div
        class="relative"
        ref="containerTarget"
        :style="{
            height: containerHeight + 'px'
        }"
    >
        <template v-if="columnWidth && data.length">
            <div
                v-for="(item, index) in data"
                :key="nodeKey ? item[nodeKey] : index"
                class="m-waterfall-item absolute duration-300"
                :style="{
                    width: columnWidth + 'px',
                    left: item._style?.left + 'px',
                    top: item._style?.top + 'px'
                }"
            >
                <slot
                    :item="item"
                    :width="columnWidth"
                    :index="index"
                />
            </div>
        </template>
        <!-- 数据渲染 -->
        <!-- 加载中 -->
    </div>
</template>

<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { getImgElements, getAllImg, onComplateImgs, getMinHeightColumn, getMinHeight, getMaxHeight } from './utils'
const props = defineProps({
    //数据源
    data: {
        type: Array,
        default: () => [],
        required: true
    },
    //唯一标识
    nodeKey: {
        type: String
    },
    // 列数
    column: {
        default: 1,
        type: Number
    },
    // 列间距
    columnSpacing: {
        default: 20,
        type: Number
    },
    // 行间距
    rowSpacing: {
        default: 20,
        type: Number
    },
    // 是否需要进行图片预读取
    picturePreReading: {
        default: true,
        type: Boolean
    }
})

//容器总高度
const containerHeight = ref(0)

//记录每列的高度
const columnHeightObj = ref({})

//获取容器的实例
const containerTarget = ref(null)

//容器的宽度(不包含 padding margin border)
const containerWidth = ref(0)

//容器的左边距 计算 item 的 left
const containerLeft = ref(0)
//列宽
const columnWidth = ref(0)
// item高度集合
let itemHeights = []

// 挂载完毕就计算列宽
onMounted(() => {
    useColumnWidth()
})

//组件销毁时去掉所有的_style
onUnmounted(() => {
    props.data.forEach((ele) => {
        delete ele._style
    })
})

//列间距的合计
const columnSpaceTotal = computed(() => {
    return (props.column - 1) * props.columnSpacing
})
//构建记录每列高度的对象
const useColumnHeightObj = () => {
    columnHeightObj.value = {}
    for (let i = 0; i < props.column; i++) {
        columnHeightObj.value[i] = 0
    }
}

//计算容器的宽度
const useContainerWidth = () => {
    const { paddingLeft, paddingRight } = getComputedStyle(containerTarget.value, null)
    //最大盒子的左padding
    containerLeft.value = parseFloat(paddingLeft)

    //放五列盒子的容器的总宽度columnWidth = 最大盒子的宽度减去左右padding
    containerWidth.value = containerTarget.value.offsetWidth - parseFloat(paddingLeft) - parseFloat(paddingRight)
}

//计算列宽
const useColumnWidth = () => {
    //计算容器宽度
    useContainerWidth()
    //计算列宽  （容器的宽度-每列的间距的总和）/ 列数
    columnWidth.value = (containerWidth.value - columnSpaceTotal.value) / props.column
}

// 监听图片加载完成(需要图片预加载)
const waitImgComplate = () => {
    itemHeights = []
    //拿到所有的元素
    let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
    //获取到元素的img 标签
    const imgElements = getImgElements(itemElements)
    //获取所有img标签的图片地址
    const allImgs = getAllImg(imgElements)
    // 等待图片加载完成
    onComplateImgs(allImgs).then(() => {
        // 图片加载完成
        itemElements.forEach((el) => {
            itemHeights.push(el.offsetHeight)
        })
        //渲染位置
        useItemLoation()
    })
}

//不需要图片预加载
const useItemHeight = () => {
    itemHeights = []
    //拿到所有元素
    let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
    //计算item高度
    itemElements.forEach((el) => {
        itemHeights.push(el.offsetHeight)
    })
    //渲染位置
    useItemLoation()
}

// 渲染位置
const useItemLoation = () => {
    props.data.forEach((item, index) => {
        //避免重复计算
        if (item._style) {
            return
        }

        item._style = {}

        //处理 left
        item._style.left = getItemLeft()
        // 处理 top
        item._style.top = getItemTop()

        //指定列高度自增
        increasingHeight(index)
    })
    //指定容器的高度
    containerHeight.value = getMaxHeight(columnHeightObj.value)
}

/**
 * 返回下一个item的left
 */
const getItemLeft = () => {
    //拿到最小高度的列
    const column = getMinHeightColumn(columnHeightObj.value)
    return column * (columnWidth.value + props.columnSpacing) + containerLeft.value
}

/**
 * 返回下一个item的top
 */
const getItemTop = () => {
    return getMinHeight(columnHeightObj.value)
}

//指定的列高度自增
const increasingHeight = (index) => {
    //最小高度所在的列
    const minHeightColumn = getMinHeightColumn(columnHeightObj.value)
    //使该列自增
    columnHeightObj.value[minHeightColumn] += itemHeights[index] + props.rowSpacing
}

// 重置
const rest = () => {
    setTimeout(() => {
        //重新计算列宽
        useColumnWidth()
        //重置所有的定位数据
        props.data.forEach((item) => {
            item._style = null
        })
    }, 500)
}

//触发计算
watch(
    () => props.data,
    (newval) => {
        nextTick(() => {
            //第一次获取数据时，构建高度记录容器
            const restColumnHeight = newval.every((item) => !item._style)
            if (restColumnHeight) {
                //构建高度记录容器
                useColumnHeightObj()
            }
            //是否需要图片预加载
            if (props.picturePreReading) {
                waitImgComplate()
            } else {
                useItemHeight()
            }
        })
    },
    {
        deep: true,
        immediate: true
    }
)

//监听列数的变化
watch(
    () => props.column,
    () => {
        if (props.picturePreReading) {
            columnWidth.value = 0
            rest()
        } else {
            rest()
        }
    }
)
</script>
<style lang="scss" scope></style>
